#pragma once

#include <sstream>
#include <string>
#include <vector>
#include <tuple>
#include <typeinfo>
#include <mysql/mysql.h>
#include "util.hpp"
#include "connect.hpp"

namespace easymysql
{
class result
{
public:
    result(MYSQL* handler)
        : _hdr(handler)
        , _res(nullptr)
    {
        store_result();
    }

    result(const connection& conn)
        : _hdr(conn.handler())
        , _res(nullptr)
    {
        store_result();
    }

    template<typename Tuple>
    bool get(Tuple* container)
    {
        MYSQL_ROW mysql_row = mysql_fetch_row(_res);
        if (mysql_row == nullptr)
            return false;

        std::vector<std::string> row = trans_row(mysql_row, mysql_num_fields(_res));

        tuple_parser<Tuple, std::tuple_size<Tuple>::value - 1>::parser(*container, row);
        return true;
    }

    template<typename tuple>
    optional<tuple> get()
    {
        MYSQL_ROW mysql_row = mysql_fetch_row(_res);
        if (mysql_row == nullptr)
            return optional<tuple>();

        std::vector<std::string> row = trans_row(mysql_row, mysql_num_fields(_res));

        tuple container;
        tuple_parser<tuple, std::tuple_size<tuple>::value - 1>::parser(container, row);
        return optional<tuple>(std::move(container));
    }

    // template<typename tuple>
    std::vector<std::string> get_field_names()
    {
        std::vector<std::string> ret;

        MYSQL_FIELD* fields = mysql_fetch_fields(_res);
        int cols = mysql_num_fields(_res);

        for (int i = 0; i < cols; ++i)
            ret.push_back(fields[i].name);

        return ret;
    }

    ~result()
    {
        mysql_free_result(_res);
    }

private:
    template<class tuple, std::size_t N>
    struct tuple_parser
    {
        using value_type = typename std::tuple_element<N, tuple>::type;

        static void parser(tuple& container, const std::vector<std::string>& values)
        {
            tuple_parser<tuple, N - 1>::parser(container, values);
            std::get<N>(container) = to_type<value_type>(values[N]);
        }
    };

    // Specialized versions are used to terminate recursion.
    template<class tuple>
    struct tuple_parser<tuple, 0>
    {
        using value_type = typename std::tuple_element<0, tuple>::type;

        static void parser(tuple& container, const std::vector<std::string>& values)
        {
            std::get<0>(container) = to_type<value_type>(values[0]);
        }
    };

    bool store_result()
    {
        return (_res = mysql_store_result(_hdr));
        // {
        //     throw except(std::string("get result failed: ") + mysql_error(_hdr));
        // }
    }

    std::vector<std::string> trans_row(const MYSQL_ROW row, int size)
    {
        std::vector<std::string> v;

        for (int i = 0; i < size; ++i)
            v.push_back(row[i]);

        return v;
    }

    template<typename value_type>
    static value_type to_type(const std::string& s)
    {
        value_type value;
        if (!(std::istringstream(s) >> value))
        {
            throw except(std::string("conversion failed value_type: ") \
                    + typeid(value_type).name());
        }
        return value;
    }

private:
    MYSQL*     _hdr;
    MYSQL_RES* _res;
};
} // namespace easymysql
